unit main;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, FileUtil, ListFilterEdit, SynHighlighterHTML,
  SynHighlighterCss, SynHighlighterPHP, SynMemo, SynHighlighterCpp, IpHtml,
  Ipfilebroker, Forms, Controls, Graphics, Dialogs, Menus, ExtCtrls, ComCtrls,
  ShellCtrls, StdCtrls, LCLIntf, LazHelpHTML,
  {$IFDEF MSWINDOWS} comobj, windows, activex, EventSink, {$ENDIF}
  tagedit;

type

  THTMLFile = record
    Editor: TSynMemo;
    Name: string;
    Filename: TFilename;
    TempFile: TFilename;
    Modified: Boolean;
    FileExt: string;
  end;

  { TMainForm }

  TMainForm = class(TForm)
    EditorMenu: TPopupMenu;
    MenuItem10: TMenuItem;
    miEditFind: TMenuItem;
    miEditReplace: TMenuItem;
    miEditSearch: TMenuItem;
    MenuItem8: TMenuItem;
    MenuItem9: TMenuItem;
    miEditPaste: TMenuItem;
    MenuItem11: TMenuItem;
    MenuItem2: TMenuItem;
    MenuItem4: TMenuItem;
    MenuItem5: TMenuItem;
    MenuItem6: TMenuItem;
    MenuItem7: TMenuItem;
    miEditSelAll: TMenuItem;
    miEditCut: TMenuItem;
    miEditCopy: TMenuItem;
    miCut1: TMenuItem;
    SaveMenuItem: TMenuItem;
    Panel1: TPanel;
    ProjectHeaderPanel: TPanel;
    PreviewButton: TButton;
    FilesPageControl: TPageControl;
    PageControl1: TPageControl;
    ShellTreeView1: TShellTreeView;
    SitesButton: TButton;
    ImageList1: TImageList;
    MainMenu1: TMainMenu;
    FileMenu: TMenuItem;
    MenuItem1: TMenuItem;
    puSaveMenuItem: TMenuItem;
    puClosepageMenuItem: TMenuItem;
    NewCSSMenuItem: TMenuItem;
    NewJSMenuItem: TMenuItem;
    NewHTMLMenuItem: TMenuItem;
    NewMenuItem: TMenuItem;
    OpenDialog: TOpenDialog;
    OpenMenuItem: TMenuItem;
    EditorTabMenu: TPopupMenu;
    SaveDialog: TSaveDialog;
    SynCppSyn1: TSynCppSyn;
    SynCssSyn1: TSynCssSyn;
    SynHTMLSyn1: TSynHTMLSyn;
    SynPHPSyn1: TSynPHPSyn;
    PreviewPage: TTabSheet;
    SourcePage: TTabSheet;
    ToolBar1: TToolBar;
    ToolBarPanel: TPanel;
    Panel2: TPanel;
    MainPanel: TPanel;
    SaveAsMenuItem: TMenuItem;
    MenuItem3: TMenuItem;
    ExitMenuItem: TMenuItem;
    HTMLToolBar: TToolBar;
    Splitter1: TSplitter;
    StatusBar: TStatusBar;
    ToolButton1: TToolButton;
    ToolButton2: TToolButton;
    ToolButton3: TToolButton;
    ToolButton4: TToolButton;
    ToolButton5: TToolButton;
    ToolButton6: TToolButton;
    ToolButton7: TToolButton;
    procedure ExitMenuItemClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
    procedure miEditFindClick(Sender: TObject);
    procedure MenuItem9Click(Sender: TObject);
    procedure miEditPasteClick(Sender: TObject);
    procedure miEditCopyClick(Sender: TObject);
    procedure miEditCutClick(Sender: TObject);
    procedure miEditSearchClick(Sender: TObject);
    procedure miEditSelAllClick(Sender: TObject);
    procedure NewCSSMenuItemClick(Sender: TObject);
    procedure NewHTMLMenuItemClick(Sender: TObject);
    procedure NewJSMenuItemClick(Sender: TObject);
    procedure OpenMenuItemClick(Sender: TObject);
    procedure PreviewButtonClick(Sender: TObject);
    procedure puClosepageMenuItemClick(Sender: TObject);
    procedure SaveAsMenuItemClick(Sender: TObject);
    procedure SaveMenuItemClick(Sender: TObject);
    procedure SitesButtonClick(Sender: TObject);
    procedure TagButtonClick(Sender: TObject);
  private
    { private declarations }
    HTMLFiles: array  of THTMLFile;
    {$IFDEF MSWINDOWS}
    EventSink1: TEventSink;
    {$ENDIF}
    WebBrower:variant;
    function CreateNewEditor: integer;
    {$IFDEF MSWINDOWS}
    procedure EventSink1Invoke(Sender: TObject; DispID: Integer;
       const IID: TGUID; LocaleID: Integer; Flags: Word;
       Params: tagDISPPARAMS; VarResult, ExcepInfo, ArgErr: Pointer);
    {$ENDIF}
  public
    { public declarations }
    function IsInTag( ed: TSynMemo; CaretPos: integer; var Coords: TPoint ): Boolean;
    procedure EditTag( HTMLTag: string; tagstart, tagend: integer );

    {parse aTag and return results as TTaginfo. aTag is an HTML opening tag without brackets.}
    function ParseTag( aTag: string ): TTagInfo;
  end;

  THTMLtag = array[0..1] of string[10];

  function GetTempFile( Dir: TFilename ): string;


var
  MainForm: TMainForm;

  HTMLtags : array [0..5] of THTMLtag = (('<p>', '</p>'), ('<em>', '</em>'), ('<strong>', '</strong>'),
                ('<br>', #13#10), ('<img>', ''), ('<a>', '</a>'));


implementation

uses ftpmanage, md5, SynEditTypes, searchdoc, searchfiles, searchresults;

{$R *.lfm}

{ TMainForm }
type
  PIUnknown=^IUnknown;
  {$IFDEF MSWINDOWS} TAtlAxAttachControl = function(Control:IUnknown; hwind:hwnd;ppUnkContainer:PIUnknown): HRESULT; stdcall; {$ENDIF}

function GetTempFile(Dir: TFilename): string;
var
  RndSrc: String;
  x: Integer;
  fn: String;
begin
  if Length(Dir)=0 then Dir := GetTempDir(false);
  if Dir[Length(Dir)] <> DirectorySeparator then Dir := Dir + DirectorySeparator;
  repeat
  fn := '~';
  RndSrc := FloatToStr(Now);
  fn :=  Dir + fn + MD5Print( MD5String(RndSrc) ) + '.html';
  until not FileExistsUTF8(fn);
  GetTempFile := fn;
end;

  {IEventIntfEvents = dispinterface
    ['{EAB22AC2-30C1-11CF-A7EB-0000C05BAE0B}']
  end;                                         }


procedure TMainForm.OpenMenuItemClick(Sender: TObject);
var
  Page: TTabSheet;
  Editor: TSynMemo;
  FileIdx: Integer;
  FileExt: String;
  Doc: THTMLFile;
begin
  if OpenDialog.Execute then
     begin
       FileIdx := CreateNewEditor;
       Doc := HTMLFiles[FileIdx];
       Editor := Doc.Editor;
       Editor.Lines.LoadFromFile(OpenDialog.FileName);
       Doc.Name := ExtractFileName(OpenDialog.FileName);
       FilesPageControl.Pages[FileIdx].Caption := Doc.Name;
       FileExt := AnsiLowerCase(ExtractFileExt(OpenDialog.FileName));
       Doc.FileExt := FileExt;
       with Editor do
            begin
              if (FileExt='.htm') or (FileExt='.html')
                 then Highlighter := SynHTMLSyn1
              else if (FileExt='.js')
                 then Highlighter := SynCppSyn1
              else if (FileExt='.css')
                 then Highlighter := SynCssSyn1
              else if (FileExt='.php')
                 then Highlighter := SynPHPSyn1;
            end;
       Doc.Filename := OpenDialog.FileName;
       if FileExists(Doc.TempFile) then DeleteFile(PChar(Doc.TempFile));
       Doc.TempFile := GetTempFile(ExtractFilePath(Doc.Filename));
       HTMLFiles[FilesPageControl.PageIndex] := Doc;
       if ShellTreeView1.Root='' then ShellTreeView1.Root := ExtractFileDir(OpenDialog.FileName);
     end;
  HTMLToolBar.Enabled := FilesPageControl.PageCount>0;
  ToolBar1.Enabled := FilesPageControl.PageCount>0;
{  PreviewPage.TabVisible := FilesPageControl.PageCount>0;
  if PreviewPage.TabVisible then WebBrower.Navigate(OleVariant(OpenDialog.FileName)); }
end;

procedure TMainForm.PreviewButtonClick(Sender: TObject);
var
  Doc: THTMLFile;
begin
  Doc := HTMLFiles[FilesPageControl.PageIndex];
  Doc.Editor.Lines.SaveToFile(Doc.TempFile);
  OpenURL(Doc.TempFile);
end;

procedure TMainForm.puClosepageMenuItemClick(Sender: TObject);
var
  Doc: THTMLFile;
  x: Integer;
begin
  Doc := HTMLFiles[FilesPageControl.PageIndex];
  Doc.Editor.PopupMenu := nil;
  Doc.Editor.Free;
  For x := FilesPageControl.PageIndex to Length(HTMLFiles)-2 do
      HTMLFiles[x] := HTMLFiles[x+1];
  SetLength(HTMLFiles, Length(HTMLFiles)-1);
  FilesPageControl.ActivePage.Free;
  ToolBar1.Enabled := FilesPageControl.PageCount>0;
end;

procedure TMainForm.SaveAsMenuItemClick(Sender: TObject);
var
  Doc: THTMLFile;
begin
  Doc := HTMLFiles[FilesPageControl.PageIndex];
  with SaveDialog do
       begin
         DefaultExt := Doc.FileExt;
         if Doc.Filename<>''
            then FileName := Doc.Filename
         else FileName := Doc.Name;
         if Execute then
            begin
              Doc.Editor.Lines.SaveToFile(SaveDialog.FileName);
              Doc.Filename := SaveDialog.FileName;
              if FileExists(Doc.TempFile) then DeleteFile(Pchar(Doc.TempFile));
              Doc.TempFile := GetTempFile(ExtractFilePath(Doc.Filename));
              Doc.Editor.Lines.SaveToFile(Doc.TempFile);
              Doc.Name := ExtractFileName(Doc.Filename);
              HTMLFiles[FilesPageControl.PageIndex] := Doc;
              FilesPageControl.ActivePage.Caption := Doc.name;
            end;
       end;
end;

procedure TMainForm.SaveMenuItemClick(Sender: TObject);
var
  Doc: THTMLFile;
begin
  Doc := HTMLFiles[FilesPageControl.PageIndex];
  if Length( Doc.Filename )>0 then
     begin
       Doc.Editor.Lines.SaveToFile(Doc.Filename);
       Doc.Editor.Modified := False;
     end
  else SaveAsMenuItemClick(Sender);
end;

procedure TMainForm.SitesButtonClick(Sender: TObject);
begin
  FTPManager.Show;
end;

procedure TMainForm.NewCSSMenuItemClick(Sender: TObject);
var
  FileIdx: Integer;
begin
  FileIdx := CreateNewEditor;
  with HTMLFiles[FileIdx] do
      begin
        TTabSheet(Editor.Parent).Caption := 'Untitled Stylesheet';
        Name := 'Untitled';
        Editor.Highlighter := SynCssSyn1;
        FileExt := '.css';
      end;
  ToolBar1.Enabled := FilesPageControl.PageCount>0;
end;

procedure TMainForm.NewHTMLMenuItemClick(Sender: TObject);
var
  FileIdx: Integer;
begin
  FileIdx := CreateNewEditor;
  with HTMLFiles[FileIdx] do
      begin
        Editor.Lines.Add('<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">');
        Editor.Lines.Add('<html><head><meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">');
        Editor.Lines.Add('<title>Untitled Document</title>');
        Editor.Lines.Add('</head><body>');
        Editor.Lines.Add('</body></html>');
        TTabSheet(Editor.Parent).Caption := 'Untitled Document';
        Name := 'Untitled';
        Editor.Highlighter := SynHTMLSyn1;
        FileExt := '.html';
      end;
  HTMLToolBar.Enabled := true;
end;

procedure TMainForm.NewJSMenuItemClick(Sender: TObject);
var
  FileIdx: Integer;
begin
  FileIdx := CreateNewEditor;
  with HTMLFiles[FileIdx] do
      begin
        Editor.Lines.Add('// Empty Javascript file');
        TTabSheet(Editor.Parent).Caption := 'Untitled Javascript file';
        Name := 'Untitled';
        Editor.Highlighter := SynCppSyn1;
        FileExt := '.js';
      end;
  ToolBar1.Enabled := true;
end;

procedure TMainForm.FormKeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
var
  Selection: TPoint;
begin
  if ssCtrl in Shift   THEN
     case Key of
     69: if IsInTag(HTMLFiles[ FilesPageControl.PageIndex ].Editor,
                               0, Selection)
      //then ShowMessage(Copy(HTMLFiles[ FilesPageControl.PageIndex ].Editor.Text, Selection.x, Selection.y-Selection.x+1));
     then EditTag(Copy(HTMLFiles[ FilesPageControl.PageIndex ].Editor.Text, Selection.x+1, Selection.y-Selection.x-1), Selection.x, Selection.y);
     end;
end;

procedure TMainForm.miEditFindClick(Sender: TObject);
begin
  FindForm.ActiveControl := FindForm.FindBox;
  if FindForm.ShowModal=mrOK
     then HTMLFiles[FilesPageControl.PageIndex].Editor.SearchReplace(FindForm.FindBox.Text, '', []);
end;

procedure TMainForm.MenuItem9Click(Sender: TObject);
begin
  with HTMLFiles[FilesPageControl.PageIndex].Editor do
      begin

      end;
end;

procedure TMainForm.miEditPasteClick(Sender: TObject);
begin
  HTMLFiles[FilesPageControl.PageIndex].Editor.PasteFromClipboard;
end;

procedure TMainForm.miEditCopyClick(Sender: TObject);
begin
  HTMLFiles[FilesPageControl.PageIndex].Editor.CopyToClipboard;
end;

procedure TMainForm.miEditCutClick(Sender: TObject);
begin
  HTMLFiles[FilesPageControl.PageIndex].Editor.CutToClipboard;
end;

procedure TMainForm.miEditSearchClick(Sender: TObject);
begin
  if SearchFilesForm.ShowModal=mrOK then
     begin
       if SearchResultsForm.DoAdvancedSearch(SearchFilesForm.FindBox.Text,
                                     SearchFilesForm.ReplaceBox.Text,
                                     SearchFilesForm.DirectoryEdit1.Text,
                                     SearchFilesForm.FilemaskEdit.Text)>0
       then SearchResultsForm.Show;
     end;
end;

procedure TMainForm.miEditSelAllClick(Sender: TObject);
begin
  HTMLFiles[FilesPageControl.PageIndex].Editor.SelectAll;
end;

procedure TMainForm.ExitMenuItemClick(Sender: TObject);
begin
  Close;
end;

procedure TMainForm.FormCreate(Sender: TObject);
var
  atl:hmodule;
  {$IFDEF MSWINDOWS} AtlAxAttachControl:TAtlAxAttachControl; {$ENDIF}
begin
 { atl:=LoadLibrary('atl.dll');
  AtlAxAttachControl:=TAtlAxAttachControl(GetProcAddress(atl, 'AtlAxAttachControl'));
  WebBrower:=CreateOleObject('Shell.Explorer');
  AtlAxAttachControl(WebBrower,panel1.Handle,nil);
  WebBrower.GoHome;  }
end;

procedure TMainForm.FormDestroy(Sender: TObject);
var
  x: Integer;
  Doc: THTMLFile;
begin
  for x := Length(HTMLFiles)-1 downto 0 do
      begin
        Doc := HTMLFiles[x];
        if FileExists(Doc.TempFile) then DeleteFile(Pchar(Doc.TempFile));
        Doc.Editor.Free;
      end;
end;

procedure TMainForm.TagButtonClick(Sender: TObject);
var
  Selection: TPoint;
  sectionstart: integer;
begin
  with HTMLFiles[FilesPageControl.PageIndex].Editor do
      begin
        sectionstart := SelStart;
        SelText := HTMLtags[TToolButton(Sender).ImageIndex, 0] + SelText + HTMLtags[TToolButton(Sender).ImageIndex, 1];
        case TToolButton(Sender).ImageIndex of
               { <img> } 4,
               { <a>   } 5 : begin
                              HTMLFiles[FilesPageControl.PageIndex].Editor.SelStart := sectionstart+1;
                              if IsInTag(HTMLFiles[ FilesPageControl.PageIndex ].Editor,
                               0, Selection)
      //then ShowMessage(Copy(HTMLFiles[ FilesPageControl.PageIndex ].Editor.Text, Selection.x, Selection.y-Selection.x+1));
     then EditTag(Copy(HTMLFiles[ FilesPageControl.PageIndex ].Editor.Text, Selection.x+1, Selection.y-Selection.x-1), Selection.x, Selection.y);
                            end;
               else SelStart := SelStart-Length(Trim(HTMLtags[TToolButton(Sender).ImageIndex, 1]));
        end;

      end;
end;

function TMainForm.CreateNewEditor: integer;
var
  Page: TTabSheet;
  Editor:TSynMemo;
begin
  Page := FilesPageControl.AddTabSheet;// TTabSheet.Create(self);
  FilesPageControl.ActivePage := Page;
  Editor := TSynMemo.Create(self);
  with Editor do
       begin
         Parent := Page;
         Align := alClient;
         PopupMenu := EditorMenu;
       end;
  SetLength(HTMLFiles,FilesPageControl.PageCount);
  HTMLFiles[FilesPageControl.PageIndex].Editor := Editor;
  HTMLFiles[FilesPageControl.PageIndex].TempFile := GetTempFile('');
  HTMLFiles[FilesPageControl.PageIndex].Editor.Lines.SaveToFile(HTMLFiles[FilesPageControl.PageIndex].TempFile);
  Result := FilesPageControl.PageIndex;

end;

{$IFDEF MSWINDOWS}
procedure TMainForm.EventSink1Invoke(Sender: TObject; DispID: Integer;
  const IID: TGUID; LocaleID: Integer; Flags: Word; Params: tagDISPPARAMS;
  VarResult, ExcepInfo, ArgErr: Pointer);

begin
{  case dispid of
    $00000066: StatusBar.Caption:=Utf8Encode(OleVariant(Params.rgvarg[0]));
  end;}
end;
{$ENDIF}
function TMainForm.IsInTag(ed: TSynMemo; CaretPos: integer; var Coords: TPoint
  ): Boolean;
var
  st1: Integer;
  en1: Integer;
  startfound: Boolean;
  endFound: Boolean;
  testtxt: String;
begin
  Result := False;
  en1 := ed.SelStart;
  st1 := en1-1;
  testtxt := Copy(ed.Text, st1, 20);
  while (st1>0) and (not startfound) do
        begin
          if ed.Text[st1]='>' then
             begin
               Result := false;
               Exit;
             end;
          if ed.Text[st1]='<' then
             begin
               startfound := true;
               testtxt := Copy(ed.Text, st1, 20);
             end;
          if not startfound then Dec(st1);
          testtxt := Copy(ed.Text, st1, 20);
        end;
  endFound := false;
  if startfound then
     begin
       while (en1<Length(ed.Text)) and (not endFound) do
         begin
           if ed.Text[en1]='<' then
              begin
                Result := False;
                Exit;
              end;
           if ed.Text[en1]='>' then
              begin
                endFound := true;
              end;
           if not endFound then Inc(en1);
         end;
       if endFound then
          begin
            Coords.x := st1;
            Coords.y := en1;
            Result := true;
          end;
     end;
end;

procedure TMainForm.EditTag(HTMLTag: string; tagstart, tagend: integer);

begin
  HTMLTag := Trim(HTMLTag);
  if Length(HTMLTag)<1 then Exit;
  if HTMLTag[1]='/' then Exit;
  TagEditor := TTagEditor.Create(nil);
  TagEditor.SetTag(ParseTag(HTMLTag));
  if TagEditor.ShowModal=mrOK then
     with HTMLFiles[FilesPageControl.PageCount-1].Editor do
          begin
            SelStart := tagstart;
            SelEnd := tagend+1;
            SelText := TagEditor.GetTag;
            SelStart := SelEnd;
          end;
  TagEditor.Free;
end;

function TMainForm.ParseTag(aTag: string): TTagInfo;
var
  p: SizeInt;
  attrib: String;
  attribcount: Integer;
  pp: SizeInt;
  delimiter: char;
  delimiterfound: Boolean;
  function IdentifyAttribute( attr: string ): TAttribute;
  begin
    Result := taNone;
    attr := AnsiLowerCase(attr);
    if attr='id' then Result := taID
    else if attr='class' then Result := taClass
    else if attr='href' then Result := taHref;
  end;
begin
  attribcount := 0;
  for p := 1 to Length(aTag) do // replace whitespace by spaces: will probably cause more problems
      if (aTag[p]in [#0..#31]) then aTag[p] := #32;
  aTag := aTag + #32;
  p := Pos(#32, aTag);
  Result.Tag := AnsiLowerCase( Copy(aTag, 1, p-1) );
  Delete(aTag, 1, p);
  aTag := TrimLeft(aTag);

  // now look for attributes
  p := pos('=', aTag);
  while p>0 do
     begin
       Inc(attribcount);
       SetLength(Result.Attributes, attribcount);
       attrib := TrimTag(Copy(aTag, 1, p-1));
       Result.Attributes[attribcount-1].Name := attrib;
       Result.Attributes[attribcount-1].Attribute := IdentifyAttribute(attrib);
       Delete(aTag, 1, p);
       aTag := TrimLeft(aTag);
       delimiter := aTag[1];
       delimiterfound := false;
       if not (delimiter in [#34, #39]) then delimiter := #32;
        p := 2;
        while (p<Length(aTag)) and not delimiterfound do
           begin
             if (aTag[p]=delimiter) and (aTag[p-1]<>'\')
                then delimiterfound := true
             else Inc(p);
           end;

       if delimiterfound then
           begin
             attrib := copy(aTag, 2, p-2);
             aTag := TrimLeft(aTag);
           end
       else p := Length(aTag);
       Result.Attributes[attribcount-1].Value := TrimTag( attrib );
       Delete(aTag, 1, p);
       p := Pos('=', aTag);
     end;
end;

end.
